Skip to content

perf(assert): fork-free arg join in core string assertions#846

Merged
Chemaclass merged 1 commit into
mainfrom
perf/assert-fork-removal
Jul 22, 2026
Merged

perf(assert): fork-free arg join in core string assertions#846
Chemaclass merged 1 commit into
mainfrom
perf/assert-fork-removal

Conversation

@Chemaclass

Copy link
Copy Markdown
Member

🤔 Background

Related #844

The core string assertions forked a subshell ($(printf '%s\n' "${arr[@]}")) on every call just to join their haystack arguments. Multiplied across thousands of assertions, that fork is the dominant per-assertion cost.

💡 Changes

  • Add bashunit::assert::join_to_slot, a fork-free return-slot join that mirrors the old subshell exactly (newline-join + strip trailing newlines).
  • Wire it into assert_contains/assert_not_contains, assert_matches/assert_not_matches, assert_string_starts_with/assert_string_ends_with and their negations (7 sites). Join step ~35× faster (10.6s → 0.3s / 20k iters), one fewer fork per call, identical behaviour.
  • assert_line_count intentionally keeps the subshell — its line-count logic depends on that exact form (noted in perf(assert): remove per-assertion array-join fork in assert.sh #844).
  • Characterization tests pin the preserved contract: trailing newlines ignored, internal newlines preserved, multi-arg haystacks joined with newlines.

Sibling issue #845 (lowercase forks in assert_contains_ignore_case) was investigated and closed with benchmark evidence — herestring measured marginally slower and combining operands is unsafe for multiline data.

https://claude.ai/code/session_01JSeB8UzLCpqst55hAK6dED

The core string assertions joined their haystack args with a
command-substitution fork ($(printf '%s\n' "${arr[@]}")) on every call.
Introduce bashunit::assert::join_to_slot, a fork-free return-slot helper that
joins with newlines and strips trailing newlines, exactly mirroring the old
subshell output. Wired into assert_contains/not_contains, assert_matches/
not_matches and assert_string_starts_with/ends_with (+negations) — 7 sites.

Join step ~35x faster (10.6s -> 0.3s over 20k iterations) and one fewer fork
per assertion, with identical behaviour. assert_line_count is intentionally
left on the subshell (line-count logic depends on that exact form).

Characterization tests pin the preserved contract: trailing newlines ignored,
internal newlines preserved, multi-arg haystacks joined with newlines.

Closes #844

Claude-Session: https://claude.ai/code/session_01JSeB8UzLCpqst55hAK6dED
@Chemaclass Chemaclass added the refactoring Refactoring or cleaning related label Jul 22, 2026
@Chemaclass Chemaclass self-assigned this Jul 22, 2026
@Chemaclass
Chemaclass merged commit e3adc9b into main Jul 22, 2026
37 checks passed
@Chemaclass
Chemaclass deleted the perf/assert-fork-removal branch July 22, 2026 20:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

refactoring Refactoring or cleaning related

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant